home *** CD-ROM | disk | FTP | other *** search
/ 8bitfiles.net/archives / archives.tar / archives / compuserve-file-archive / 05 Programming / BFCSRC.TUT < prev    next >
Text File  |  2019-04-13  |  36KB  |  768 lines

  1.    *====================================================================*
  2.    *Everything you wanted to know about Forth (but were afraid to ask). *
  3.    *        Copyright (c) 1986, by Scott Ballantyne.        *
  4.    *====================================================================*
  5.  
  6.     This file contains a description of how a threaded-code Forth
  7. compiler works, with specific reference to Blazin' Forth.  You don't need
  8. to know the stuff in this file, unless you are interested in the particulars
  9. of how Forth compilers work, or are interested in improving or changing one.
  10. Specifically, I wrote the following as an aid to people who might be
  11. trying to understand the source to Blazin' Forth, and it should be considered
  12. part of the documentation for the source files to the system.
  13.  
  14. To understand this document, you will need a decent knowledge of
  15. Forth. An understanding of pointers won't hurt any either.
  16.  
  17. You don't need to know machine language to understand the stuff in this file,
  18. but you will, of course, need it to understand the actual source.
  19.  
  20. I have attempted to provide sample code in hi-level forth that illustrates
  21. the routines involved.  This code is similar, but not exactly the same, as
  22. the actual machine level routines in the actual compiler.  In particular,
  23. you should not expect these routines to actually work if you type them into
  24. a forth system in an attempt to build a "Forth in Forth".  They are
  25. provided to add clarity, and that is their only function.
  26.  
  27. ---------------------------------------------------------------------------
  28. ------------------------ What is a Virtual Machine? -----------------------
  29. ---------------------------------------------------------------------------
  30.  
  31.     A Virtual Machine is a creation, in software, of a piece of hardware.
  32. Note that this hardware does not actually have to exist - it is only within
  33. the last year that an actual hardware Forth computer has been built. Forth
  34. has been around a lot longer than that.
  35.  
  36. All high level languages are essentially virtual machines, since they
  37. implement instructions which are not part of the actual hardware CPU.
  38. As an example, Forth uses two stacks, a parameter stack, and a return stack.
  39. On an actual hardware Forth computer, the built-in machine language of the
  40. computer would contain instructions for manipulating each stack, and the
  41. pointers to the bottom of each stack. On the 6502, there is actually only
  42. one stack - one or the other of the Forth stacks must be emulated using
  43. software routines.  So you could say that one stack is a hardware stack,
  44. and the other stack is a Virtual stack.
  45.  
  46.     As another example, the function call mechanism (how the actual
  47. functions, procedures, or words are eventually caused to execute) of a
  48. high level language is rarely directly supported by hardware instructions.
  49. On the majority of CPU's in use on personal computers today, the only
  50. real function call mechanism implemented in the hardware is the
  51. subroutine call (usually referred to as a JSR or CALL instruction). This
  52. instruction usually only saves the return address automatically.  Any other
  53. information or any other method of invoking a subroutine must be done by
  54. software that, essentially, is a software (or virtual) function call, as
  55. opposed to the hardware function call and return.  This is particularly true
  56. of threaded code Forth, and the routine which implements this function call
  57. mechanism is called NEXT.  Understanding NEXT is the key to understanding
  58. the functioning of the Forth compiler at its lowest level.
  59.  
  60. --------------------------------------------------------------------------
  61. -------------------------- Introducing NEXT. -----------------------------
  62. --------------------------------------------------------------------------
  63.  
  64.     To understand how NEXT (and the various Forth machine registers that
  65. NEXT uses to do its thing) works, let's first take a quick look at the
  66. structure of a compiled higher level Forth word.  It is essentially a list
  67. of addresses:
  68.  
  69. : EXAMPLE   W1 W2 W3 ;
  70.  
  71.     ( Standard Forth Header goes here)
  72.         Address of W1
  73.         Address of W2
  74.         Address of W3
  75.         Address of EXIT ( ; )
  76.  
  77. NEXT uses several auxiliary registers to keep track of where the user
  78. program is. On a CPU with many registers, these would be kept in a selected
  79. CPU register.  On the 6502, which has only 4 user accessible registers,
  80. these are maintained as virtual registers (page zero locations are used
  81. for greater speed).  One of these virtual registers is called the
  82. Interpreter Pointer, or IP for short, and it is responsible for keeping
  83. track of the progress of the current program.  When NEXT is entered, in the
  84. course of running a program, the IP will be pointed at the word we want
  85. to execute. NEXT does some stuff (to be described in a moment) to cause the
  86. this word to begin to execute, but before transferring control to this
  87. word, it moves the IP ahead to the next words address, so it will know what
  88. word to run the next time it is called.
  89.  
  90. Here is a sample execution of EXAMPLE, given above:
  91.  
  92. IP --> Address of W1 ( NEXT executes W1, first moving IP ahead to W2)
  93. IP --> Address of W2 ( NEXT executes W2, first moving IP ahead to W3)
  94. IP --> Address of W3 ( NEXT executes W3, first moving IP ahead to EXIT)
  95. IP --> Address of EXIT 
  96.  
  97. I like to think of the IP as a kind of Address Slider, that can be moved
  98. ahead or behind to direct the flow of execution of the current program.
  99.  
  100. Ultimately, of course, NEXT must cause machine language instructions to be
  101. executed, which essentially means changing the hardware program counter of
  102. the CPU to point to the appropriate batch of instructions to be executed.
  103. It does this using another virtual register called the Current Word Pointer,
  104. or W for short.  To understand this portion of NEXT, we have to clear up
  105. exactly what we mean by "Address of Word" in the above discussion.
  106.  
  107. The individual members of the list that makes up a Forth definitions
  108. executable body are the addresses of the code field in the header of the
  109. compiled word.  (These "addresses of code fields" will be refered to as
  110. "the execution address" of a word in the rest of this document. When the
  111. term "code field" is used, the reference will be to the actual code field
  112. portion of a dictionary header. Or, at least, that is how I am going to
  113. try to use these terms.)
  114. This execution address (as you may recall) itself stores an address which
  115. points to machine level (assembly language) instructions.
  116. It is these instructions that NEXT causes the CPU to execute, by forcing
  117. the CPU's program counter to the address stored at the execution address of
  118. that word. So we have a couple of levels of indirection here:
  119.  
  120.     The IP points to a location which holds the execution address of a
  121.     word.
  122.     The execution address pointed to by the location pointed to by the IP
  123.     points to executable machine language instructions.
  124.  
  125. So the full story of NEXT is as follows:
  126.     1) NEXT retrieves the value stored at the address in the IP.
  127.     2) It saves this value ( the execution address of a word)
  128.        in W (the current word pointer).
  129.     3) It then moves the IP ahead to point at the next word to be
  130.        executed.
  131.     4) Finally, it forces the hardware program counter to the value
  132.        stored at the address in W, which causes machine level
  133.        instructions to execute.
  134.  
  135. Here is an example of the full execution of a forth word.
  136.  
  137. Let's make up some example addresses for our example execution:
  138.  
  139. Address Contents    Description
  140. -----------------------------------------------------------------------
  141. $A000    [ $0600 ]   W1's execution address is $A000, and contains $0600.
  142. $B000    [ $0700 ]   W2's execution address is $B000, and contains $0700.
  143. $C000    [ $0800 ]   W3's execution address is $C000, and contains $0800.
  144. $0900   [ $0880 ]   EXIT's execution address is $0900, and contains $0880.
  145.  
  146. And here is how the compiled EXAMPLE word from earlier looks - let's say
  147. that the body of example starts at $E000:
  148.  
  149. Address     Contents    Desription
  150. ----------------------------------
  151. $E000   [ $A000 ]    Compiled W1
  152. $E002   [ $B000 ]    Compiled W2
  153. $E004    [ $C000 ]    Compiled W3
  154. $E006    [ $0900 ]    Compiled EXIT.
  155.  
  156. So, at the entry to NEXT, the IP will contain $E000.
  157.  
  158. NEXT fetches the address stored here, and stuffs it into W, so W will now
  159. contain $A000, which is the execution address of W1.
  160.  
  161. NEXT now increments the IP to point at the next word, so the IP will contain
  162. $E002.
  163.  
  164. Finally, NEXT forces the program counter of the CPU to the address stored
  165. in the address stored in W.  So here's a quickie quiz - what will be the
  166. address in the hardware program counter?
  167.  
  168. (Answer: $0600 - which is the address of the machine language code for W1).
  169.  
  170. Here is a quick synopsis of the values stored in the IP, W, and hardware
  171. PC for the execution of EXAMPLE, given above.  It might be a good idea
  172. to pause here, and try to run through the rest of the example on your own,
  173. to check your understanding (and the clarity of my explanation) of how
  174. NEXT functions.
  175.  
  176. Word-to-Execute     IP        W       IP-AT-EXIT   PC
  177. ----------------------------------------------------------
  178.     W1            $E000     $A000      $E002      $0600
  179.     W2          $E002     $B000       $E004      $0700
  180.     W3          $E004     $C000      $E006      $0800
  181.     EXIT          $E006     $0900      $E008      $0880
  182.  
  183. As a final aid to understanding, here is an implementation of NEXT in
  184. hi-level Forth:
  185.  
  186.     : NEXT    IP    // Get address of IP
  187.         @    // Get value of IP (address of next word to execute)
  188.         @    // Get that words execution address
  189.         W !    // And stuff into the current word pointer.
  190.         2 IP +!    // Move IP along to next word, for next time.
  191.         W @    // Get the execution address from W.
  192.         @    // Get the actual address of the code.
  193.         PC !    // Force into hardware PC, so that it will execute.
  194.         ;
  195.  
  196. Now that you understand NEXT (I hope), and the role of the Forth registers
  197. IP and W, you are in a good position to understand the rest of the
  198. Forth system.
  199.  
  200. ---------------------------------------------------------------------------
  201. ---------------- EXECUTE - or how Forth launches programs -----------------
  202. ---------------------------------------------------------------------------
  203.  
  204.     You might be wondering at this point exactly how an application gets
  205. launched in the first place.  Since NEXT uses the IP, and assumes that the
  206. IP is pointing at a compiled execution address, how do words that you just
  207. type in from the terminal get executed?  Obviously, words typed directly
  208. to the interpreter from the terminal don't have an address which is valid
  209. for the IP.
  210.     The answer is the Forth word EXECUTE, which takes an execution
  211. address as it's argument.  When you type a word to the interpreter that
  212. it can find in the dictionary, it pushes the execution address of the
  213. word onto the parameter stack, and calls EXECUTE.  Execute first saves this
  214. execution address in W, and then forces the PC to the address stored in
  215. this execution address, just like the last part of NEXT.  Here is EXECUTE
  216. in high level forth:
  217.  
  218.     : EXECUTE   ( execution-address   ---  )
  219.         W !    // save in W - then do last part of NEXT
  220.         W @ @    // get the address of the code to execute
  221.         PC !    // and execute it.
  222.         ;
  223.  
  224.     Note that EXECUTE does not call NEXT - it assumes the EXECUTEd word
  225. will be doing that.
  226.  
  227. At this point you are no doubt wondering how the IP gets initialized at all.
  228. It's not hard to understand, but let's put off a detailed discussion of it
  229. until we talk about the DOCOLON and EXIT routines, a little further on, but
  230. here is a brief hint:  When EXECUTE executes your word, there is already a
  231. valid value in the IP - it is pointing somewhere inside INTERPRET.  If the
  232. word you are executing is a colon definition, then the first thing it does
  233. is save the current value of the IP, and then changes it to point to itself.
  234. A CODE definition won't change the IP at all (unless the code you write is
  235. supposed to), and so the pointer to inside INTERPRET just hangs around until
  236. the code defintion gets to it's NEXT call, which causes the INTERPRET word
  237. to resume. This will all become clearer when you understand exactly how
  238. DOCOLON and EXIT work.
  239.  
  240. Incidentally, there is also an EXECUTE inside of the compiler loop - it's
  241. there to handle IMMEDIATE definitions - the ones that execute even when you
  242. are compiling. The logic here is the same as above. The only difference is
  243. that the IP will be pointing somewhere inside ] , instead of inside
  244. INTERPRET.
  245.  
  246. ----------------------------------------------------------------------------
  247. ------------------------- How Forth Does Branching -------------------------
  248. ----------------------------------------------------------------------------
  249.  
  250.     In our discussion of NEXT, above, we only talked about sequential
  251. execution of words.  What happens if we need to branch around words ( as
  252. we do in conditionals like IF) or cause the same words to be executed
  253. repeatedly ( as we do in DO LOOP or BEGIN UNTIL constructs)?
  254.  
  255.     The answer is actually very simple - we just change the IP to point
  256. to the word we want to branch to, and then execute NEXT.  If you followed the
  257. above discussion on NEXT, it should be obvious that this causes a complete
  258. diversion of the flow of control for the current word.
  259.  
  260.     When a branch is compiled, two things are done: a special word that
  261. controls the branch is compiled, and the destination address of the branch
  262. is compiled.  For example:
  263.  
  264. : CR'S    BEGIN  CR  AGAIN ;
  265.  
  266. This word will just print newlines, until a rude action is taken by the
  267. operator to stop it.  Here is how the compiled word looks in memory.
  268.  
  269.     (Standard Forth Header goes here)
  270. $A000    CR    ( execution address of CR)
  271. $A002    BRANCH    ( execution address of BRANCH)
  272. $A004    $A000    ( address to BRANCH to)
  273. $A006    EXIT    ( execution address of EXIT)
  274.  
  275. When CR'S executes, NEXT executes CR, and then it executes BRANCH.
  276. BRANCH takes the address immediately following it in memory, in this case
  277. $A000, and stuffs it into the IP.  BRANCH then JMP's to NEXT.  Since the
  278. IP is once again pointing at CR, (having been changed by BRANCH), NEXT
  279. once again executes CR, and then BRANCH, which causes the IP to be changed,
  280. and so on, forever.
  281.  
  282. BRANCH is an example of an unconditional branching primitive - it always
  283. branches, no matter what.  ?BRANCH is a conditional branching word - it
  284. will branch if the value on the top of the stack is FALSE - otherwise,
  285. no branch takes place.  Here is an example of a word that would cause ?BRANCH
  286. to be compiled:
  287.  
  288. : CR?  ( BOOLEAN -- )  IF  CR  THEN ;
  289.  
  290. CR? will obviously print a CR if the top of the stack is non-zero, otherwise,
  291. nothing happens.  Here is how CR? would look in memory:
  292.  
  293.     (Standard Forth Header)
  294. $A000    ?BRANCH    ( execution address of ?BRANCH)
  295. $A002    $A006    ( destination address of branch )
  296. $A004    CR    ( execution address of CR)
  297. $A006    EXIT    ( execution address of EXIT)
  298.  
  299. In this case, the execution would execute ?BRANCH first, which tests the
  300. value of the top of the stack. Notice that two things can happen here,
  301. BOTH of which will change the IP:
  302.  
  303.     1) If the top of the stack is FALSE, ?BRANCH will force the IP
  304.        beyond the branch address, by adding two. This will obviously
  305.        cause CR to be executed.
  306.     2) If the top of the stack is TRUE, ?BRANCH will act exactly like
  307.        BRANCH, and stuff $A006 (the word immediately following ?BRANCH)
  308.        into the IP, which will obviously just EXIT the definition.
  309.  
  310. In any branching word, one or the other of these two things will happen.
  311. All of the branching words are compiled in exactly this way, with the
  312. branching primitive first, and the destination address of the branch
  313. immediately following it in memory.  The reason that there are more
  314. branching primitives in Blazin' Forth than just these two has more to do with
  315. entry and exit conditions that it does with the actual branching mechanism.
  316.  
  317. For example, IF-THEN, IF-ELSE-THEN, BEGIN-UNTIL, BEGIN-WHILE-REPEAT,
  318. BEGIN-AGAIN are all implemented with combinations of ?BRANCH and BRANCH,
  319. since all of these involve boolean testing of the top of the stack.
  320.  
  321. Things like DO-LOOP and ?DO-LOOP and DO-+LOOP, etc. have additional things
  322. to do, like move the loop parameters to the return stack, add or subtract
  323. the loop index, test the loop index, and clean up the return stack on
  324. the loop exit.  But the actual mechanics of branching are exactly the same,
  325. only the entry/exit conditions differ from word to word.  Among other
  326. advantages, it makes the compiler code much simpler, since there are fewer
  327. 'special cases' to check for.
  328.  
  329. Once again, as an aid to understanding, here are sample implementations of
  330. BRANCH and ?BRANCH in hi-level forth. As you read these, keep in mind that
  331. when BRANCH or ?BRANCH is executing, the IP will be pointing at the branch
  332. address - since it gets incremented before the execution of the next word
  333. by NEXT:
  334.  
  335. : BRANCH ( branch unconditionally STACK: -- )
  336.     IP @    // Get the value of IP, ordinarily the address of the code
  337.         // field of the next word to execute. In this case, it is
  338.         // a branch address.
  339.     IP @    // Get the value stored at the address - which is the
  340.         // destination branch value.
  341.     IP !    // Change the IP to the destination address.
  342.     NEXT    // and execute.
  343.     ;
  344.  
  345. : ?BRANCH ( conditional branch STACK: BOOLEAN --  )
  346.     0= IF        // test top of stack - if FALSE ( equal to zero)
  347.          BRANCH    // just execute BRANCH
  348.        ELSE        // value was TRUE, don't branch.
  349.          2 IP +!    // move IP over branch address, to next word.
  350.     THEN
  351.     NEXT        // and execute.
  352.     ;
  353.  
  354. ----------------------------------------------------------------------------
  355. ---------------- How Forth Does Nesting - DOCOLON and EXIT -----------------
  356. ----------------------------------------------------------------------------
  357.  
  358. In the above examples there was never any question of remembering where we
  359. came from - the course of execution of the word was changed, and we never
  360. really cared to remember what called what. But what about having one colon
  361. definition calling another one?  How does Forth remember where to come
  362. back to when it has finished the called definition?
  363.  
  364. This is not particularly difficult either.  Once again, the IP and W, the
  365. current word pointer, play central roles.  In what follows, remember that
  366. W points to the actual address of the word we want to execute, while the IP
  367. points to a memory location which contains the address of the word.
  368.  
  369. What happens is this:
  370. NEXT starts to execute a colon definition.  All colon definitions have the
  371. same address stored at their execution address, which is the address of a
  372. machine language routine called DOCOLON or NEST.  It is this routine that
  373. is responsible for saving the current execution environment.
  374.  
  375. DOCOLON first pushes the current value of the IP (which holds the address
  376. of the word we want to return to) onto the return stack.  At this point,
  377. W will be holding the execution address of the new word to execute. We want
  378. to execute the body of this word, so DOCOLON now adds two to the value in W,
  379. which makes it point to the BODY of this definition, and stuffs it into the
  380. IP. DOCOLON now calls NEXT, which causes the new word to execute.
  381.  
  382. Eventually, NEXT will execute EXIT, which is the word compiled by ; .
  383. EXIT's job is to restore the previous execution environment, and it does
  384. this by very simply by pulling the top of the return stack, and stuffing it
  385. into the IP.  It then calls NEXT, which causes the calling word to resume
  386. execution as though nothing had happened.
  387.  
  388. Here is an example:
  389.  
  390. : FOOBAR    CR   ;
  391.  
  392. : COLON-CALL   FOOBAR ;
  393.  
  394.  
  395. Compiled view of the above:
  396.  
  397.     (Header for FOOBAR)
  398. $A000    DOCOLON        ( Code field portion of header)
  399. $A002    CR        ( Body )
  400. $A004    EXIT
  401.  
  402.     (Header for COLON-CALL)
  403. $B000    DOCOLON        ( Code field portion of header)
  404. $B002    FOOBAR        ( Body)
  405. $B004    EXIT
  406.  
  407. And here is a simplified execution of COLON-CALL.
  408.  
  409. Word-to-Execute     IP        W       IP-AT-EXIT  RETURN-STACK
  410. -------------------------------------------------------------------------
  411. FOOBAR               $B002    $A000      $B004         XXXXX
  412. DOCOLON                $B004    $A000     $A002         $B004
  413.   CR               $A002    CR's EA  $A004        $B004
  414.   EXIT               $A004    EXIT EA  $B004         XXXXX
  415. EXIT (in CALL-COLON)   $B004    EXIT EA   -----         ------
  416.  
  417. (NOTE: EA stands for Execution Address.)
  418.  
  419. Once again, here is a sample implementation in higher level forth, of
  420. DOCOLON and EXIT:
  421.  
  422. : DOCOLON    IP @    // get current value of IP
  423.          >R        // Save it on return stack
  424.          W @    // Get execution address of current word.
  425.          2+        // Convert to Address of body.
  426.          IP !    // Change IP
  427.          NEXT    // Execute new word
  428.          ;
  429.  
  430. : EXIT        R<        // Get old IP (saved by DOCOLON)
  431.         IP !    // Restore
  432.         NEXT    // Resume execution.
  433.         ;
  434.  
  435. Since the most recent caller is always at the top of the return stack, the
  436. forth system can find it's way through any number of levels of nesting, no
  437. matter how deep.  There is no theoretical limit to the depth of nesting of
  438. forth words, although there is the practical limit of the size of the return
  439. stack.
  440.  
  441. So how deeply can one nest definitions in Blazin' Forth?  Well, the obvious
  442. answer is around 123 levels, since there is an entire page of memory
  443. allocated for the return stack. It is equally obvious that certain actions
  444. can modify this, such as pushing literals to the return stack in your
  445. definitions, or using DO LOOPS, since DO LOOPS store the loop control
  446. information on the return stack.
  447.  
  448. Less obviously, you should note that CODE definitions do not cause the above
  449. nesting to occur.  The majority of the primitives in Blazin' Forth are
  450. CODE definitions, and the desire to maximize the level of nesting was one
  451. of the design considerations that led to this decision.
  452.  
  453. In practice, I have never even approached the theoretical maximum level for
  454. nesting, much less had a crash that was traceable to return stack overflow,
  455. even when using highly recursive words.
  456.  
  457. ----------------------------------------------------------------------------
  458. ------------------------- Forth's DOES> construct --------------------------
  459. ----------------------------------------------------------------------------
  460.  
  461.     The implementation of the DOES> feature of Forth is usually one of
  462. the hardest for people to understand. The thing to remember when we get down
  463. to the actual details of the implementation is exactly how the current word
  464. pointer W works.  When a word is executing, W will contain the execution
  465. address of that word.  Stored at the address in W is the actual address of
  466. the code that is executing.  In Forth, we would say that W @ is the execution
  467. address of the word, and W @ @ is the address of the code. Keeping this
  468. in mind will help you to understand what is going on.
  469.  
  470.     First, a quick refresher on what DOES> does.  DOES> is possibly the
  471. most unique feature of forth, since it allows you to extend the actual
  472. Forth compiler to compile new types of words.  DOES> words are compiler
  473. words, and as such, they are used to create new words to execute. To help
  474. keep the discussion clear, lets call words which contain DOES> parent words,
  475. and words which are created by DOES> words, child words.
  476.  
  477.     When a parent word executes, it creates a dictionary entry for the
  478. child.  When the child executes, it leaves the address of it's body on the
  479. parameter stack, and then executes the hi-level forth words after DOES> in
  480. the parent word.  A common way to teach beginners about DOES> is to redefine
  481. one of the Forth primitives, such as CONSTANT, as a DOES> word.  I'll do the
  482. same thing here, but I will also try to explain exactly how these words
  483. do their thing on an implementation level.
  484.  
  485. : CONSTANT   CREATE ,   DOES>  @ ;
  486.  
  487.     Here we have our CONSTANT definition.  When CONSTANT (the parent)
  488. executes, it will create a dictionary entry with a standard header (that's
  489. the function of the CREATE in our definition).  It then allocates two bytes
  490. of parameter space, and compiles the value on the top of the stack into the
  491. dictionary (that's the function of the , in our definition).  The words
  492. following the DOES> don't do anything when CONSTANT executes - they execute
  493. when the child word (the word created by CONSTANT) executes.
  494.     When the child executes, it will leave the address of it's body on
  495. the stack, and then the words following the DOES> will be executed.  In this
  496. example, there is only the @ - which will replace the address of the BODY
  497. with the value stored there, just like CONSTANT should, and EXIT, which will
  498. return us to wherever we came from.
  499.  
  500. Thus
  501.  
  502. 10 CONSTANT TEN
  503.  
  504. creates a dictionary entry for the name TEN, and a 2 byte parameter field for
  505. the value 10, which CONSTANT also stuffs there.
  506.  
  507. Executing
  508.  
  509. TEN
  510.  
  511. will first leave the address of TEN's body on the stack, and then the words
  512. following DOES> (in the parent word CONSTANT) will execute, which result in
  513. the value 10 being left.
  514.  
  515. Now for the implementation details:
  516.  
  517. Here is how our definition of CONSTANT would look in the dictionary:
  518.  
  519.     (Preceeded, as always, with the standard forth header )
  520.     $A000    DOCOL        ( code field portion of header)
  521.     $A002    CREATE        ( execution address )
  522.     $A004    ,        ( execution address )
  523.     $A006    (;CODE)        ( execution address )
  524.     $A008    JMP DODOES    ( actual machine language instructions)
  525.     $A00B    @        ( execution address)
  526.     $A00D    EXIT        ( execution address)
  527.  
  528. And here is how the definition of TEN would look:
  529.  
  530.     ( Standard dictionary header goes here)
  531.     $B000    $A008    ( code field portion of header)
  532.     $B002    10    ( value stored in parameter field)
  533.  
  534. Ok, here is how it all sorts out.  Remember that DOES> is defined as an
  535. IMMEDIATE word, and so it executes when you are compiling.  The mysterious
  536. portion of the CONSTANT defintion, above - the (;CODE) and the JMP DODOES
  537. are written into the dictionary whenever DOES> executes.
  538.  
  539. (;CODE) is an unusual primitive.  When it executes, it overwrites the current
  540. contents of the code field of the last word added to the dictionary with
  541. the address of the machine code which follows it in the defintion currently
  542. executing.  In our example above, it will cause all words created with
  543. CONSTANT to have a code field whose value is $A008 - the address of the
  544. JMP instruction in CONSTANT.  This will obviously cause the JMP DODOES
  545. instruction to be executed each time a word created by CONSTANT is executed.
  546.  
  547. DODOES is the routine that does the actual magic. It must do three things:
  548.  
  549.     1) It must save the current value of the IP (just like DOCOLON) so
  550.        Forth knows how to get back to the caller.
  551.     2) It must push the address of the child's body to the stack.
  552.     3) It must execute the words following the JMP DODOES in the parent.
  553.  
  554. Using TEN as an example, DODOES must push the value $B002 to the parameter
  555. stack, and it must then cause the words starting at $A00B to be executed.
  556.  
  557. Here is how it's done in Blazin' Forth:
  558.  
  559. When TEN executes, it should be clear that the value stored in the current
  560. word pointer ( W ) is $B000, which is the execution address of TEN.  The
  561. IP will be pointing somewhere important, so DODOES first saves it, which it
  562. does exactly like DOCOLON, by pushing it onto the return stack.
  563.  
  564. Once the IP has been safely tucked away, we have two tasks to perform.  We
  565. must push the address of the parameter field of TEN to the stack, and we
  566. must then cause the hi-level forth words in the DOES> part of CONSTANT to
  567. execute.  We can use the value of W to do both these things.
  568.  
  569. Remember that W, the current word pointer, is currently pointing at the
  570. execution address of TEN, and so contains $B000.  So it is a simple matter to
  571. calculate the address of the body of 10 - we just add two to the current
  572. value of W ( which gives us $B002), and push it to the parameter stack.
  573.  
  574. Now, the value stored at $B000 is $A008, which is the address of the
  575. JMP DODOES instruction in CONSTANT.  We want to execute the hi-level forth
  576. words beyond this instruction - a piece of cake.  We simply add 3 ( the size
  577. of an absolute JMP instruction on the 6502 ) to the value stored at the
  578. execution address of the child word TEN, and stuff it into the IP.  Once this
  579. has been done, all we need to do is call NEXT, which takes care of everything
  580. else, since we just pointed the IP at the proper spot.
  581.  
  582. Since we saved the previous value of the IP first off, when the EXIT
  583. at the end of the DOES> stuff is executed, we get returned to whatever called
  584. us.
  585.  
  586. Once again, here is an example of DODOES in hi-level forth:
  587.  
  588. : DODOES    IP @ >R        // Save current IP on return stack
  589.         W @ 2+        // Leave address of parameter field on stack
  590.         W @ @        // Get address of JMP DODOES instruction
  591.         3 +        // Add in size of JMP absolute instruction
  592.         IP !        // Set as new execution address
  593.         NEXT        // and execute it.
  594.         ;
  595.  
  596. That wasn't so hard, was it?
  597.  
  598. ----------------------------------------------------------------------------
  599. ------------------- LITERALS, CONSTANTS, and VARIABLES ---------------------
  600. ----------------------------------------------------------------------------
  601.  
  602. In this last section, I talk about how Blazin' Forth handles compiled
  603. literals, and how the words defined by CONSTANT, VARIABLE and USER are
  604. implemented.
  605.  
  606. There are two kinds of literals recognized by Forth, numeric and string.
  607. Numeric literals are compiled automagically, by the compiler loop, while
  608. string literals are compiled by ." (usually).
  609.  
  610. Numeric literals first.  As you probably remember, when you compile a
  611. definition, Forth attempts to look up each word in the definition in the
  612. dictionary.  If it finds the word, then it compiles the execution address
  613. of the word into the dictionary (unless the word is defined IMMEDIATE, of
  614. course!). If the word is not found, then it attempts to convert the string
  615. of characters you just fed it into a number.  If it succeeds, then it
  616. compiles a special primitive called (LIT) into the dictionary, and
  617. immediately past that, it places the value of your literal. (If it can't
  618. convert it to a number, then it issues the famous "NOT IN CURRENT SEARCH
  619. ORDER" message.)
  620.  
  621. Here is an example:
  622.  
  623. : BIG   1000 ;
  624.  
  625. and here is how it looks in the dictionary:
  626.  
  627.     (standard forth header)
  628. $A000    (LIT)    ( start of BIG's BODY)
  629. $A002    1000    ( The value of your literal)
  630. $A004    EXIT    ( EXIT - Tadah!)
  631.  
  632. (LIT)'s function is to place the value following it in memory on the top
  633. of the parameter stack, and to move the IP over the literal value, to the
  634. next valid forth word.  It's pretty simple in practice, if you remember
  635. that if (LIT) is being executed, the IP must be pointing at the address
  636. of the literal, since it was incremented by NEXT.  Here it is as an
  637. example FORTH definition:
  638.  
  639. : (LIT)        ( -- 16bit)
  640.     IP @ @        // Get value of literal to stack.
  641.     2 IP +!        // Move IP past literal value, to next valid word.
  642.     NEXT        // and call NEXT
  643.     ;
  644.  
  645. Blazin' Forth has a memory saving feature for values that will fit in one
  646. byte.  For these values another word, called CLIT is compiled, instead of
  647. (LIT).  It works very similarly to (LIT):
  648.  
  649. : CLIT    ( --- byte-value)
  650.     IP @ C@        // get the byte to the parameter stack
  651.     1 IP +!        // move over byte literal to next valid word.
  652.     NEXT        // and execute next
  653.     ;
  654.  
  655. The case of string literals is very similar.  ." is an immediate word which
  656. first compiles (.") . It then searches the input stream for an ending ", and
  657. moves everything before this final quote into the dictionary, with a leading
  658. count byte, as is normal for Forth. It also moves the pointers to the
  659. input stream past the string, so the interpreter won't try to evaluate it.
  660. Here is an example:
  661.  
  662. : GREETING   ." HELLO" ;
  663.  
  664. And here is how GREETING would look in memory:
  665.  
  666.     (Header)
  667. $A000    (.")    ( primitive to print the following inline string)
  668. $A002    5    ( the length of the string)
  669. $A003    H E L L O ( The characters are stored here, one per byte )    
  670. $A008    EXIT
  671.  
  672. The (.") primitive is one of the few low level words in Blazin' Forth that
  673. is actually written in Forth (i.e. it's a colon definition).  Since (.")
  674. is a colon definition, this means that when (.") is called, DOCOLON will
  675. save the current value of the IP on the return stack.  But, by a pretty
  676. stroke of fate, this will be exactly the address of the string following
  677. (.").  To get a little more concrete about it:
  678.  
  679. When Greeting executes, the IP will eventually contain the value $A000. This
  680. will cause NEXT to execute (."), but NEXT will first, as always, bump the
  681. IP to $A002 (the start of the inline string).  When (.") executes, since it
  682. is a colon definition, DOCOLON will push $A002 (the current IP) to the
  683. return stack, and then enter the definition.  So at entry, we have the
  684. address of the string on the return stack.  All we have to do is retrieve the
  685. address, use COUNT and TYPE to display it, and adjust the return address
  686. on the stack before we exit.  Once the return address has been adjusted and
  687. placed back on the stack, EXIT will return us to the word past the end of
  688. the inline string.
  689.  
  690. Here is (."), just as it appears in the Blazin' Forth:
  691.  
  692. : (.")   ( --- )
  693.     R@        ( get string address from return stack)
  694.     COUNT         ( get the count byte, adjust address )
  695.     DUP 1+        ( total length of string, including count byte)
  696.     R> +        ( get address, move past end of string)
  697.     >R        ( and restore, for EXIT)
  698.     TYPE        ( the string)
  699.     ;        ( and return, using adjusted address as return)
  700.  
  701. So much for literals.
  702.  
  703. Constants and variables (including USER variables) run time action is
  704. determined by the routines pointed to by their code fields.  There is no
  705. special primitives compiled, as there is with the literals.
  706.  
  707. Here is a short run down of the actions of each:
  708. Constants place the value stored in their body on the parameter stack.
  709. Variables place the address of their body on the parameter stack.
  710. User variables place the address of the associated variable on the stack. The
  711. actual value stored in the parameter field is an offset from a base address.
  712.  
  713. Armed with your present knowledge of the IP and W, understanding these
  714. definitions should be a snap. They all work very much the same. We start with
  715. variable, since it's the simplest.
  716.  
  717. When the code field of a variable (or constant or USER) is executed, W
  718. will contain the execution address (the address of the code field) of the
  719. word in question.  So it's easy: take the value in W, add two, and leave that
  720. value on the stack. Here is a hi-level definition of DOVARIABLE:
  721.  
  722. : DOVARIABLE  ( -- address)
  723.     W @    // Get the execution address of this variable
  724.     2+    // Add two to get the body.
  725.     NEXT    // and that's it!
  726.     ;
  727.  
  728. Constants are very similar to variables - the only difference is the extra
  729. step required to retrieve the value in the constants body. Here is a hi-level
  730. definition of DOCONSTANT:
  731.  
  732. : DOCONSTANT  ( -- value)
  733.     W @ 2+    // As in variable - get the address of the body.
  734.     @    // Get the value stored there.
  735.     NEXT
  736.     ;
  737.  
  738. USER variables are very similar to constants. The only addition here is that
  739. we add the base address of the user area to the value stored in the body of
  740. the user variable.
  741.  
  742. : DOUSER ( -- address )
  743.     W @    // get execution address of this user variable
  744.     2+ C@    // get offset - we only use byte offsets in Blazin' Forth.
  745.     UP @    // get base address of user area
  746.     +    // add to offset to get actual address of variable
  747.     NEXT
  748.     ;
  749.  
  750. Here is a question for those who want to test their general comprehension
  751. of the topics discussed here.  Why can't we use the IP instead of W in the
  752. definitions of VARIABLE, CONSTANT, and USER ?
  753.  
  754. Answer:
  755. Aside from making the definition more complex, it would be impossible to
  756. retrieve the addresses of variables, or the values of constants, when we
  757. are typing their names directly into the interpreter from the terminal!
  758. Remember that the interpreter launches programs by stuffing the
  759. execution address of a word into W.  In the following situation, there is
  760. no way to get from the address of the IP to the address of the parameter
  761. field:
  762.  
  763. VARIABLE BLETCH
  764. BLETCH . XXXX
  765.  
  766. since the IP is still pointing somewhere inside INTERPRET.  The only pointer
  767. that is valid to code such as DOVARIABLE in all cases is W.
  768.